// LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's
// CodeMirror 1 mode.
// highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting

CodeMirror.defineMode("lua", function (config, parserConfig) {
  var indentUnit = config.indentUnit;

  function prefixRE(words) {
    return new RegExp("^(?:" + words.join("|") + ")", "i");
  }

  function wordRE(words) {
    return new RegExp("^(?:" + words.join("|") + ")$", "i");
  }

  var specials = wordRE(parserConfig.specials || []);

  // long list of standard functions from lua manual
  var builtins = wordRE([
    "_G", "_VERSION", "assert", "collectgarbage", "dofile", "error", "getfenv", "getmetatable", "ipairs", "load",
    "loadfile", "loadstring", "module", "next", "pairs", "pcall", "print", "rawequal", "rawget", "rawset", "require",
    "select", "setfenv", "setmetatable", "tonumber", "tostring", "type", "unpack", "xpcall",

    "coroutine.create", "coroutine.resume", "coroutine.running", "coroutine.status", "coroutine.wrap", "coroutine.yield",

    "debug.debug", "debug.getfenv", "debug.gethook", "debug.getinfo", "debug.getlocal", "debug.getmetatable",
    "debug.getregistry", "debug.getupvalue", "debug.setfenv", "debug.sethook", "debug.setlocal", "debug.setmetatable",
    "debug.setupvalue", "debug.traceback",

    "close", "flush", "lines", "read", "seek", "setvbuf", "write",

    "io.close", "io.flush", "io.input", "io.lines", "io.open", "io.output", "io.popen", "io.read", "io.stderr", "io.stdin",
    "io.stdout", "io.tmpfile", "io.type", "io.write",

    "math.abs", "math.acos", "math.asin", "math.atan", "math.atan2", "math.ceil", "math.cos", "math.cosh", "math.deg",
    "math.exp", "math.floor", "math.fmod", "math.frexp", "math.huge", "math.ldexp", "math.log", "math.log10", "math.max",
    "math.min", "math.modf", "math.pi", "math.pow", "math.rad", "math.random", "math.randomseed", "math.sin", "math.sinh",
    "math.sqrt", "math.tan", "math.tanh",

    "os.clock", "os.date", "os.difftime", "os.execute", "os.exit", "os.getenv", "os.remove", "os.rename", "os.setlocale",
    "os.time", "os.tmpname",

    "package.cpath", "package.loaded", "package.loaders", "package.loadlib", "package.path", "package.preload",
    "package.seeall",

    "string.byte", "string.char", "string.dump", "string.find", "string.format", "string.gmatch", "string.gsub",
    "string.len", "string.lower", "string.match", "string.rep", "string.reverse", "string.sub", "string.upper",

    "table.concat", "table.insert", "table.maxn", "table.remove", "table.sort"
  ]);
  var keywords = wordRE(["and", "break", "elseif", "false", "nil", "not", "or", "return",
    "true", "function", "end", "if", "then", "else", "do",
    "while", "repeat", "until", "for", "in", "local" ]);

  var indentTokens = wordRE(["function", "if", "repeat", "do", "\\(", "{"]);
  var dedentTokens = wordRE(["end", "until", "\\)", "}"]);
  var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]);

  function readBracket(stream) {
    var level = 0;
    while (stream.eat("=")) ++level;
    stream.eat("[");
    return level;
  }

  function normal(stream, state) {
    var ch = stream.next();
    if (ch == "-" && stream.eat("-")) {
      if (stream.eat("["))
        return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state);
      stream.skipToEnd();
      return "comment";
    }
    if (ch == "\"" || ch == "'")
      return (state.cur = string(ch))(stream, state);
    if (ch == "[" && /[\[=]/.test(stream.peek()))
      return (state.cur = bracketed(readBracket(stream), "string"))(stream, state);
    if (/\d/.test(ch)) {
      stream.eatWhile(/[\w.%]/);
      return "number";
    }
    if (/[\w_]/.test(ch)) {
      stream.eatWhile(/[\w\\\-_.]/);
      return "variable";
    }
    return null;
  }

  function bracketed(level, style) {
    return function (stream, state) {
      var curlev = null, ch;
      while ((ch = stream.next()) != null) {
        if (curlev == null) {
          if (ch == "]") curlev = 0;
        }
        else if (ch == "=") ++curlev;
        else if (ch == "]" && curlev == level) {
          state.cur = normal;
          break;
        }
        else curlev = null;
      }
      return style;
    };
  }

  function string(quote) {
    return function (stream, state) {
      var escaped = false, ch;
      while ((ch = stream.next()) != null) {
        if (ch == quote && !escaped) break;
        escaped = !escaped && ch == "\\";
      }
      if (!escaped) state.cur = normal;
      return "string";
    };
  }

  return {
    startState:function (basecol) {
      return {basecol:basecol || 0, indentDepth:0, cur:normal};
    },

    token:function (stream, state) {
      if (stream.eatSpace()) return null;
      var style = state.cur(stream, state);
      var word = stream.current();
      if (style == "variable") {
        if (keywords.test(word)) style = "keyword";
        else if (builtins.test(word)) style = "builtin";
        else if (specials.test(word)) style = "variable-2";
      }
      if ((style != "lua-comment") && (style != "lua-string")) {
        if (indentTokens.test(word)) ++state.indentDepth;
        else if (dedentTokens.test(word)) --state.indentDepth;
      }
      return style;
    },

    indent:function (state, textAfter) {
      var closing = dedentPartial.test(textAfter);
      return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0));
    }
  };
});

CodeMirror.defineMIME("text/x-lua", "lua");
